//
// GTMNSObject+KeyValueObserving.m
//
// Copyright 2009 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License.  You may obtain a copy
// of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
// License for the specific language governing permissions and limitations under
// the License.
//

//
// MAKVONotificationCenter.m
// MAKVONotificationCenter
//
// Created by Michael Ash on 10/15/08.
//

// This code is based on code by Michael Ash.
// See comment in header.
#import "GTMDefines.h"
#import "GTMNSObject+KeyValueObserving.h"
#import "GTMDefines.h"
#import "GTMDebugSelectorValidation.h"
#import "GTMObjC2Runtime.h"
#import "GTMMethodCheck.h"

// A singleton that works as a dispatch center for KVO
// -[NSObject observeValueForKeyPath:ofObject:change:context:] and turns them
// into selector dispatches. It stores a collection of
// GTMKeyValueObservingHelpers, and keys them via the key generated by
// -dictionaryKeyForObserver:ofObject:forKeyPath:selector.
@interface GTMKeyValueObservingCenter : NSObject {
  @private
  NSMutableDictionary* observerHelpers_;
}

+(id) defaultCenter;

-(void) addObserver:(id)observer
   ofObject          :(id)target
 forKeyPath        :(NSString*)keyPath
   selector          :(SEL)selector
   userInfo          :(id)userInfo
    options           :(NSKeyValueObservingOptions)options;
-(void) removeObserver:(id)observer
   ofObject             :(id)target
 forKeyPath           :(NSString*)keyPath
   selector             :(SEL)selector;
-(id) dictionaryKeyForObserver:(id)observer
   ofObject                     :(id)target
 forKeyPath                   :(NSString*)keyPath
   selector                     :(SEL)selector;
@end

@interface GTMKeyValueObservingHelper : NSObject {
  @private
  __weak id observer_;
  SEL selector_;
  id userInfo_;
  __weak id target_;
  NSString* keyPath_;
}

-(id) initWithObserver:(id)observer
   object               :(id)target
  keyPath              :(NSString*)keyPath
 selector             :(SEL)selector
 userInfo             :(id)userInfo
  options              :(NSKeyValueObservingOptions)options;
-(void) deregister;

@end

@interface GTMKeyValueChangeNotification ()
-(id) initWithKeyPath:(NSString*)keyPath ofObject:(id)object
 userInfo            :(id)userInfo change:(NSDictionary*)change;
@end

@implementation GTMKeyValueObservingHelper

// For info how and why we use these statics:
// http://lists.apple.com/archives/cocoa-dev/2006/Jul/msg01038.html
static char GTMKeyValueObservingHelperContextData;
static char* GTMKeyValueObservingHelperContext =
  &GTMKeyValueObservingHelperContextData;

-(id) initWithObserver:(id)observer
                object:(id)target
               keyPath:(NSString*)keyPath
              selector:(SEL)selector
              userInfo:(id)userInfo
               options:(NSKeyValueObservingOptions)options {
  if ((self = [super init])) {
    observer_ = observer;
    selector_ = selector;
    userInfo_ = [userInfo retain];

    target_ = target;
    keyPath_ = [keyPath retain];

    [target addObserver:self
             forKeyPath:keyPath
                options:options
                context:GTMKeyValueObservingHelperContext];
  }
  return self;
}

-(NSString*) description {
  return [NSString stringWithFormat:
          @"%@ <observer = %@ keypath = %@ target = %@ selector = %@>",
          [self class], observer_, keyPath_, target_,
          NSStringFromSelector(selector_)];
}

#if GTM_SUPPORT_GC

-(void) finalize {
  if (target_) {
    _GTMDevLog(@"Didn't deregister %@", self);
    [self deregister];
  }
  [super finalize];
}

#endif  // GTM_SUPPORT_GC

-(void) dealloc {
  if (target_) {
    _GTMDevLog(@"Didn't deregister %@", self);
    [self deregister];
  }
  [userInfo_ release];
  [keyPath_ release];
  [super dealloc];
}

-(void) observeValueForKeyPath:(NSString*)keyPath
                      ofObject:(id)object
                        change:(NSDictionary*)change
                       context:(void*)context {
  if (context == GTMKeyValueObservingHelperContext) {
    GTMKeyValueChangeNotification* notification =
      [[GTMKeyValueChangeNotification alloc] initWithKeyPath:keyPath
                                                    ofObject:object
                                                    userInfo:userInfo_
                                                      change:change];
    [observer_ performSelector:selector_ withObject:notification];
    [notification release];
  } else {
    // COV_NF_START
    // There's no way this should ever be called.
    // If it is, the call will go up to NSObject which will assert.
    [super observeValueForKeyPath:keyPath
                         ofObject:object
                           change:change
                          context:context];
    // COV_NF_END
  }
}

-(void) deregister {
  [target_ removeObserver:self forKeyPath:keyPath_];
  target_ = nil;
}

@end

@implementation GTMKeyValueObservingCenter

+(id) defaultCenter {
  static GTMKeyValueObservingCenter* center = nil;

  if (!center) {
    // do a bit of clever atomic setting to make this thread safe
    // if two threads try to set simultaneously, one will fail
    // and the other will set things up so that the failing thread
    // gets the shared center
    GTMKeyValueObservingCenter* newCenter = [[self alloc] init];
    if (!objc_atomicCompareAndSwapGlobalBarrier(nil,
            newCenter,
            (void*)&center)) {
      [newCenter release];       // COV_NF_LINE no guarantee we'll hit this line
    }
  }
  return center;
}

-(id) init {
  if ((self = [super init])) {
    observerHelpers_ = [[NSMutableDictionary alloc] init];
  }
  return self;
}

// COV_NF_START
// Singletons don't get deallocated
-(void) dealloc {
  [observerHelpers_ release];
  [super dealloc];
}

// COV_NF_END

-(id) dictionaryKeyForObserver:(id)observer
                      ofObject:(id)target
                    forKeyPath:(NSString*)keyPath
                      selector:(SEL)selector {
  NSString* key = nil;

  if (!target && !keyPath && !selector) {
    key = [NSString stringWithFormat:@"%p:", observer];
  } else {
    key = [NSString stringWithFormat:@"%p:%@:%p:%p",
           observer, keyPath, selector, target];
  }
  return key;
}

-(void) addObserver:(id)observer
           ofObject:(id)target
         forKeyPath:(NSString*)keyPath
           selector:(SEL)selector
           userInfo:(id)userInfo
            options:(NSKeyValueObservingOptions)options {
  GTMKeyValueObservingHelper* helper =
    [[GTMKeyValueObservingHelper alloc] initWithObserver:observer
                                                  object:target
                                                 keyPath:keyPath
                                                selector:selector
                                                userInfo:userInfo
                                                 options:options];
  id key = [self dictionaryKeyForObserver:observer
                                 ofObject:target
                               forKeyPath:keyPath
                                 selector:selector];

  @synchronized(self) {
    GTMKeyValueObservingHelper* oldHelper = [observerHelpers_ objectForKey:key];

    if (oldHelper) {
      _GTMDevLog(@"%@ already observing %@ forKeyPath %@",
          observer, target, keyPath);
      [oldHelper deregister];
    }
    [observerHelpers_ setObject:helper forKey:key];
  }
  [helper release];
}

-(void) removeObserver:(id)observer
              ofObject:(id)target
            forKeyPath:(NSString*)keyPath
              selector:(SEL)selector {
  id key = [self dictionaryKeyForObserver:observer
                                 ofObject:target
                               forKeyPath:keyPath
                                 selector:selector];
  NSMutableArray* allValidHelperKeys = [NSMutableArray array];
  NSArray* allValidHelpers = nil;

  @synchronized(self) {
    NSString* helperKey;

    GTM_FOREACH_OBJECT(helperKey, [observerHelpers_ allKeys]) {
      if ([helperKey hasPrefix:key]) {
        [allValidHelperKeys addObject:helperKey];
      }
    }
#if DEBUG
    if ([allValidHelperKeys count] == 0) {
      _GTMDevLog(@"%@ was not observing %@ with keypath %@",
          observer, target, keyPath);
    }
#endif // DEBUG
    allValidHelpers = [observerHelpers_ objectsForKeys:allValidHelperKeys
                                        notFoundMarker:[NSNull null]];
    [observerHelpers_ removeObjectsForKeys:allValidHelperKeys];
  }
  [allValidHelpers makeObjectsPerformSelector:@selector(deregister)];
}

@end

@implementation NSObject (GTMKeyValueObservingAdditions)

-(void) gtm_addObserver:(id)observer
             forKeyPath:(NSString*)keyPath
               selector:(SEL)selector
               userInfo:(id)userInfo
                options:(NSKeyValueObservingOptions)options {
  _GTMDevAssert(observer && [keyPath length] && selector,
      @"Missing observer, keyPath, or selector");
  GTMKeyValueObservingCenter* center =
    [GTMKeyValueObservingCenter defaultCenter];
  GTMAssertSelectorNilOrImplementedWithArguments(
      observer,
      selector,
      @encode(GTMKeyValueChangeNotification*),
      NULL);
  [center addObserver:observer
             ofObject:self
           forKeyPath:keyPath
             selector:selector
             userInfo:userInfo
              options:options];
}

-(void) gtm_removeObserver:(id)observer
                forKeyPath:(NSString*)keyPath
                  selector:(SEL)selector {
  _GTMDevAssert(observer && [keyPath length] && selector,
      @"Missing observer, keyPath, or selector");
  GTMKeyValueObservingCenter* center =
    [GTMKeyValueObservingCenter defaultCenter];
  GTMAssertSelectorNilOrImplementedWithArguments(
      observer,
      selector,
      @encode(GTMKeyValueChangeNotification*),
      NULL);
  [center removeObserver:observer
                ofObject:self
              forKeyPath:keyPath
                selector:selector];
}

-(void) gtm_stopObservingAllKeyPaths {
  GTMKeyValueObservingCenter* center =
    [GTMKeyValueObservingCenter defaultCenter];

  [center removeObserver:self
                ofObject:nil
              forKeyPath:nil
                selector:Nil];
}

@end

@implementation GTMKeyValueChangeNotification

-(id) initWithKeyPath:(NSString*)keyPath ofObject:(id)object
             userInfo:(id)userInfo change:(NSDictionary*)change {
  if ((self = [super init])) {
    keyPath_ = [keyPath copy];
    object_ = [object retain];
    userInfo_ = [userInfo retain];
    change_ = [change retain];
  }
  return self;
}

-(void) dealloc {
  [keyPath_ release];
  [object_ release];
  [userInfo_ release];
  [change_ release];
  [super dealloc];
}

-(id) copyWithZone:(NSZone*)zone {
  return [[[self class] allocWithZone:zone] initWithKeyPath:keyPath_
                                                   ofObject:object_
                                                   userInfo:userInfo_
                                                     change:change_];
}

-(BOOL) isEqual:(id)object {
  return [keyPath_ isEqualToString:[object keyPath]] &&
         [object_ isEqual:[object object]] &&
         [userInfo_ isEqual:[object userInfo]] &&
         [change_ isEqual:[object change]];
}

-(NSString*) description {
  return [NSString stringWithFormat:
          @"%@ <object = %@ keypath = %@ userInfo = %@ change = %@>",
          [self class], object_, keyPath_, userInfo_, change_];
}

-(NSUInteger) hash {
  return [keyPath_ hash] + [object_ hash] + [userInfo_ hash] + [change_ hash];
}

-(NSString*) keyPath {
  return keyPath_;
}

-(id) object {
  return object_;
}

-(id) userInfo {
  return userInfo_;
}

-(NSDictionary*) change {
  return change_;
}

@end

#ifdef DEBUG

static void SwizzleMethodsInClass(Class cls, SEL sel1, SEL sel2) {
  Method m1 = class_getInstanceMethod(cls, sel1);
  Method m2 = class_getInstanceMethod(cls, sel2);

  method_exchangeImplementations(m1, m2);
}

# if GTM_PERFORM_KVO_CHECKS

// This is only used when GTM_PERFORM_KVO_CHECKS is on.
static void SwizzleClassMethodsInClass(Class cls, SEL sel1, SEL sel2) {
  Method m1 = class_getClassMethod(cls, sel1);
  Method m2 = class_getClassMethod(cls, sel2);

  method_exchangeImplementations(m1, m2);
}

# endif // GTM_PERFORM_KVO_CHECKS

// This category exists to attempt to help deal with tricky KVO issues.
// KVO is a wonderful technology in some ways, but is extremely fragile and
// allows developers a lot of freedom to shoot themselves in the foot.
// Refactoring an app that uses a lot of KVO can be really difficult, as can
// debugging it.
// These are some tools that we have found useful when working with KVO. Note
// that these tools are only on in Debug builds.
//
// We have divided these tools up into two categories: Checks and Debugs.
//
// Debugs
// Debugs are mainly for logging all the KVO/KVC that is occurring in your
// application. To enable our KVO debugging, set the GTMDebugKVO environment
// variable to 1 and you will get a whole pile of KVO logging that may help you
// track down problems.
// bash - export GTMDebugKVO=1
// csh/tcsh - setenv GTMDebugKVO 1
//
// Checks
// First we believe that instance variables should be private by default,
// and that any KVO should be done via accessors. Apple by default allows KVO
// to get at instance variables directly. Since our coding standards define
// that instance variables should be @private, we feel that KVO shouldn't be
// breaking this encapsulation. Unfortunately the @private, @protected
// designators are a compile time convention only, and don't get carried over
// into the runtime, so there's no way to check on an individual iVar what
// it's visibility is. We therefore assume that an instance variable is private,
// and disallow KVO access to instance variables. The problem with most KVO
// issues is that they occur at runtime and unless you execute that case you
// may never see the bug until it's too late. We try to force KVO issues to
// rear their head at the time of the observing if at all possible.
// Checks are on by default in debug builds. They can be turned off by defining
// the compile flag GTM_PERFORM_KVO_CHECKS to 0
// i.e. #define GTM_PERFORM_KVO_CHECKS 0, or set it
// in GCC_PREPROCESSOR_DEFINITIONS.
//
// Checks work at a couple of different levels.
// The most restrictive of the checks is that we set
// |accessInstanceVariablesDirectly| to NO by default. This means that if you
// attempt to perform KVO on an instance variable, you will get an exception
// thrown.
// Also, when adding an observer, we check to see if any member of the path
// starts or ends with _ which by convention denotes an instance variable. If so
// we warn you about attempting to access a ivar directly.

@interface NSObject (GTMDebugKeyValueObserving)
-(void) _gtmDebugAddObserver:(NSObject*)observer
 forKeyPath                 :(NSString*)keyPath
    options                    :(NSKeyValueObservingOptions)options
    context                    :(void*)context;
-(void) _gtmDebugArrayAddObserver:(NSObject*)observer
 toObjectsAtIndexes              :(NSIndexSet*)indexes
         forKeyPath                      :(NSString*)keyPath
            options                         :(NSKeyValueObservingOptions)options
            context                         :(void*)context;
-(void) _gtmDebugRemoveObserver:(NSObject*)observer
 forKeyPath                    :(NSString*)keyPath;
-(void) _gtmDebugArrayRemoveObserver:(NSObject*)observer
 fromObjectsAtIndexes               :(NSIndexSet*)indexes
           forKeyPath                         :(NSString*)keyPath;
-(void) _gtmDebugWillChangeValueForKey:(NSString*)key;
-(void) _gtmDebugDidChangeValueForKey:(NSString*)key;

# if GTM_PERFORM_KVO_CHECKS

-(void) _gtmCheckAddObserver:(NSObject*)observer
 forKeyPath                 :(NSString*)keyPath
    options                    :(NSKeyValueObservingOptions)options
    context                    :(void*)context;
-(void) _gtmCheckAddObserver:(NSObject*)observer
 toObjectsAtIndexes         :(NSIndexSet*)indexes
         forKeyPath                 :(NSString*)keyPath
            options                    :(NSKeyValueObservingOptions)options
            context                    :(void*)context;
+(BOOL) _gtmAccessInstanceVariablesDirectly;

# endif // GTM_PERFORM_KVO_CHECKS
@end

@implementation NSObject (GTMDebugKeyValueObserving)
GTM_METHOD_CHECK(NSObject, _gtmDebugAddObserver : forKeyPath : options : context :);
GTM_METHOD_CHECK(NSObject, _gtmDebugRemoveObserver : forKeyPath :);
GTM_METHOD_CHECK(NSObject, _gtmDebugWillChangeValueForKey :);
GTM_METHOD_CHECK(NSObject, _gtmDebugDidChangeValueForKey :);
GTM_METHOD_CHECK(NSArray,
    _gtmDebugArrayAddObserver : toObjectsAtIndexes : forKeyPath : options : context :);
GTM_METHOD_CHECK(NSArray,
    _gtmDebugArrayRemoveObserver : fromObjectsAtIndexes : forKeyPath :);

# if GTM_PERFORM_KVO_CHECKS

GTM_METHOD_CHECK(NSObject,
    _gtmCheckAddObserver : forKeyPath : options : context :);
GTM_METHOD_CHECK(NSArray,
    _gtmCheckAddObserver : toObjectsAtIndexes : forKeyPath : options : context :);
GTM_METHOD_CHECK(NSObject,
    _gtmAccessInstanceVariablesDirectly);

# endif // GTM_PERFORM_KVO_CHECKS

+(void) load {
  NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
  NSDictionary* env = [[NSProcessInfo processInfo] environment];
  id debugKeyValue = [env valueForKey:@"GTMDebugKVO"];
  BOOL debug = NO;

  if ([debugKeyValue isKindOfClass:[NSNumber class]]) {
    debug = [debugKeyValue intValue] != 0 ? YES : NO;
  } else if ([debugKeyValue isKindOfClass:[NSString class]]) {
    debug = ([debugKeyValue hasPrefix:@"Y"] || [debugKeyValue hasPrefix:@"T"] ||
             [debugKeyValue intValue]);
  }
  Class cls = Nil;
  if (debug) {
    cls = [NSObject class];
    SwizzleMethodsInClass(cls,
        @selector(addObserver:forKeyPath:options:context:),
        @selector(_gtmDebugAddObserver:forKeyPath:options:context:));
    SwizzleMethodsInClass(cls,
        @selector(removeObserver:forKeyPath:),
        @selector(_gtmDebugRemoveObserver:forKeyPath:));
    SwizzleMethodsInClass(cls,
        @selector(willChangeValueForKey:),
        @selector(_gtmDebugWillChangeValueForKey:));
    SwizzleMethodsInClass(cls,
        @selector(didChangeValueForKey:),
        @selector(_gtmDebugDidChangeValueForKey:));
    cls = [NSArray class];
    SwizzleMethodsInClass(cls,
        @selector(addObserver:toObjectsAtIndexes:forKeyPath:options:context:),
        @selector(_gtmDebugArrayAddObserver:toObjectsAtIndexes:forKeyPath:options:context:));
    SwizzleMethodsInClass(cls,
        @selector(removeObserver:fromObjectsAtIndexes:forKeyPath:),
        @selector(_gtmDebugArrayRemoveObserver:fromObjectsAtIndexes:forKeyPath:));
  }
# if GTM_PERFORM_KVO_CHECKS
  cls = [NSObject class];
  SwizzleMethodsInClass(cls,
      @selector(addObserver:forKeyPath:options:context:),
      @selector(_gtmCheckAddObserver:forKeyPath:options:context:));
  SwizzleClassMethodsInClass(cls,
      @selector(accessInstanceVariablesDirectly),
      @selector(_gtmAccessInstanceVariablesDirectly));
  cls = [NSArray class];
  SwizzleMethodsInClass(cls,
      @selector(addObserver:toObjectsAtIndexes:forKeyPath:options:context:),
      @selector(_gtmCheckAddObserver:toObjectsAtIndexes:forKeyPath:options:context:));

# endif // GTM_PERFORM_KVO_CHECKS
  [pool drain];
}

-(void) _gtmDebugAddObserver:(NSObject*)observer
                  forKeyPath:(NSString*)keyPath
                     options:(NSKeyValueObservingOptions)options
                     context:(void*)context {
  _GTMDevLog(@"Adding observer %@ to %@ keypath '%@'", observer, self, keyPath);
  [self _gtmDebugAddObserver:observer forKeyPath:keyPath
                     options:options context:context];
}

-(void) _gtmDebugArrayAddObserver:(NSObject*)observer
               toObjectsAtIndexes:(NSIndexSet*)indexes
                       forKeyPath:(NSString*)keyPath
                          options:(NSKeyValueObservingOptions)options
                          context:(void*)context {
  _GTMDevLog(@"Array adding observer %@ to indexes %@ of %@ keypath '%@'",
      observer, indexes, self, keyPath);
  [self _gtmDebugArrayAddObserver:observer
               toObjectsAtIndexes:indexes
                       forKeyPath:keyPath
                          options:options context:context];
}

-(void) _gtmDebugRemoveObserver:(NSObject*)observer
                     forKeyPath:(NSString*)keyPath {
  _GTMDevLog(@"Removing observer %@ from %@ keypath '%@'",
      observer, self, keyPath);
  [self _gtmDebugRemoveObserver:observer forKeyPath:keyPath];
}

-(void) _gtmDebugArrayRemoveObserver:(NSObject*)observer
                fromObjectsAtIndexes:(NSIndexSet*)indexes
                          forKeyPath:(NSString*)keyPath {
  _GTMDevLog(@"Array removing observer %@ from indexes %@ of %@ keypath '%@'",
      indexes, observer, self, keyPath);
  [self _gtmDebugArrayRemoveObserver:observer
                fromObjectsAtIndexes:indexes
                          forKeyPath:keyPath];
}

-(void) _gtmDebugWillChangeValueForKey:(NSString*)key {
  _GTMDevLog(@"Will change '%@' of %@", key, self);
  [self _gtmDebugWillChangeValueForKey:key];
}

-(void) _gtmDebugDidChangeValueForKey:(NSString*)key {
  _GTMDevLog(@"Did change '%@' of %@", key, self);
  [self _gtmDebugDidChangeValueForKey:key];
}

# if GTM_PERFORM_KVO_CHECKS

-(void) _gtmCheckAddObserver:(NSObject*)observer
                  forKeyPath:(NSString*)keyPath
                     options:(NSKeyValueObservingOptions)options
                     context:(void*)context {
  NSArray* keyPathElements = [keyPath componentsSeparatedByString:@"."];
  NSString* element;

  GTM_FOREACH_OBJECT(element, keyPathElements) {
    if ([element hasPrefix:@"_"] || [element hasSuffix:@"_"]) {
      _GTMDevLog(@"warning: %@ is registering an observation on what appears "
          @"to be a private ivar of %@ (or a sub keyed object) with "
          @"element %@ of keyPath %@.", observer, self, element,
          keyPath);
    }
  }
  [self _gtmCheckAddObserver:observer
                  forKeyPath:keyPath
                     options:options
                     context:context];
}

-(void) _gtmCheckAddObserver:(NSObject*)observer
          toObjectsAtIndexes:(NSIndexSet*)indexes
                  forKeyPath:(NSString*)keyPath
                     options:(NSKeyValueObservingOptions)options
                     context:(void*)context {
  NSArray* keyPathElements = [keyPath componentsSeparatedByString:@"."];
  NSString* element;

  GTM_FOREACH_OBJECT(element, keyPathElements) {
    if ([element hasPrefix:@"_"] || [element hasSuffix:@"_"]) {
      _GTMDevLog(@"warning: %@ is registering an observation on what appears "
          @"to be a private ivar of %@ (or a sub keyed object) with "
          @"element %@ of keyPath %@.", observer, self, element,
          keyPath);
    }
  }
  [self _gtmCheckAddObserver:observer
          toObjectsAtIndexes:indexes
                  forKeyPath:keyPath
                     options:options
                     context:context];
}

+(BOOL) _gtmAccessInstanceVariablesDirectly {
  // Apple has lots of "bad" direct instance variable accesses, so we
  // only want to check our code, as opposed to library code.  iOS simulator
  // builds copy the app into the user's home directory.  Xcode 4 also changes
  // the default location of the output directory.  Don't count being within
  // the user's home and under "/Library/" as being a system library.

  // If this turns out to be slow, we may want to consider a cache to speed
  // things up.
  NSBundle* bundle = [NSBundle bundleForClass:self];
  NSString* path = [bundle bundlePath];
  BOOL hasLibrary = [path rangeOfString:@"/Library/"].location != NSNotFound;
  BOOL startsWithUser = [path hasPrefix:@"/Users/"];

  return !startsWithUser && hasLibrary;
}

# endif // GTM_PERFORM_KVO_CHECKS

@end

#endif  // DEBUG
